﻿/* Author: Prahlad Yeri
 * Created: 18-07-2012
 */
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace libftp
{
	/// <summary>
	/// Abstraction of the ftp file transfer functionality
	/// </summary>
	public class ftp
	{
        public event EventHandler<downloadCompleteEventArgs> downloadComplete;
        public event EventHandler<uploadCompleteEventArgs> uploadComplete;
        public event EventHandler<downloadProgressEventArgs> downloadProgress;
        public event EventHandler<uploadProgressEventArgs> uploadProgress;
        public event EventHandler<statusChangeEventArgs> statusChange;

        public System.Threading.SynchronizationContext context;

		internal bool isConnected=false;

		
		private string _username="";
		private string _password="";

#region eventStuff

        void OnDownloadComplete(string filename)
        {
            //save delegate field in a temporary field for thread-safety
            EventHandler<downloadCompleteEventArgs> temp = downloadComplete;

            if (temp != null)
            {
                //if there is a listener who as asked for notification, then give away!
                downloadCompleteEventArgs e = new downloadCompleteEventArgs();
                e.filename = filename;
                //temp(this, e);
                context.Post(delegate(object state) { downloadComplete(this, e); }, null);
            }
        }

        void OnUploadComplete(string filename)
        {
            //save delegate field in a temporary field for thread-safety
            EventHandler<uploadCompleteEventArgs> temp = uploadComplete;

            if (temp != null)
            {
                //if there is a listener who as asked for notification, then give away!
                uploadCompleteEventArgs e = new uploadCompleteEventArgs();
                e.filename = filename;
                //uploadComplete(this, e);
                context.Post(delegate(object state) { uploadComplete(this, e); }, null);
            }
        }

        void OnDownloadProgress(string filename, long bytesTransferred)
        {
            EventHandler<downloadProgressEventArgs> temp=downloadProgress;
            if (temp!=null)
            {
                downloadProgressEventArgs e=new downloadProgressEventArgs();
                e.filename = filename;
                e.bytesTransferred = bytesTransferred;
                //downloadProgress(this, e);
                //temp(this, e);
                context.Post(delegate(object state) { downloadProgress(this, e); }, null);
            }
        }

        void OnUploadProgress(string filename, long bytesTransferred)
        {
            EventHandler<uploadProgressEventArgs> temp = uploadProgress;
            if (temp != null)
            {
                uploadProgressEventArgs e = new uploadProgressEventArgs();
                e.filename = filename;
                e.bytesTransferred = bytesTransferred;
                //uploadProgress(this, e);
                context.Post(delegate(object state) { uploadProgress(this, e); }, null);
            }
        }

        void OnStatusChange(string message, long uploaded, long downloaded)
        {
            //save delegate field in a temporary field for thread-safety
            EventHandler<statusChangeEventArgs> temp = statusChange;

            if (temp != null)
            {
                //if there is a listener who as asked for notification, then give away!
                statusChangeEventArgs e = new statusChangeEventArgs();
                e.message = message;
                e.bytesUploaded = uploaded;
                e.bytesDownloaded = downloaded;
                //statusChange(this, e);
                context.Post(delegate(object state) { statusChange(this, e); }, null);
            }
        }
#endregion

        internal ftp() 
        {
            context = SynchronizationContext.Current;
        } //hide constructor from the outside world.

        //tests connection and browse to home-directory
		public List<ftpinfo> connect(string host,string username,string password)
		{
			this._username=username;
			this._password=password;
			
			//FtpWebRequest.Create(host); //test connect;
            context = SynchronizationContext.Current;
			
			return browse(host);
		}
		
		public bool exists(string remotefilename)
		{
			return true;
		}
		
		/// <summary>
		/// Fetch all files/folders in this directory and return the ftpinfo array.
		/// </summary>
		public List<ftpinfo> browse(string path) //eg: "ftp.xyz.org/", "ftp.xyz.org/ftproot/etc"
		{
			FtpWebRequest request=(FtpWebRequest)FtpWebRequest.Create(path);
			request.Method=WebRequestMethods.Ftp.ListDirectoryDetails;
			List<ftpinfo> files=new List<ftpinfo>();
			
			//request.Proxy = System.Net.WebProxy.GetDefaultProxy();
			//request.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
            request.Credentials = new NetworkCredential(_username, _password);
			Stream rs=(Stream)request.GetResponse().GetResponseStream();

            OnStatusChange("CONNECTED: " + path, 0, 0);

			StreamReader sr = new StreamReader(rs);
			string strList = sr.ReadToEnd();
			string[] lines=null;

			if (strList.Contains("\r\n"))
			{
				lines=strList.Split(new string[] {"\r\n"},StringSplitOptions.None);
			}
			else if (strList.Contains("\n"))
			{
				lines=strList.Split(new string[] {"\n"},StringSplitOptions.None);
			}
			
			//now decode this string array

            if (lines==null || lines.Length == 0)
                return null;
			
			foreach(string line in lines)
			{
				if (line.Length==0)
					continue;
				//parse line
	        	Match m= GetMatchingRegex(line);
	        	if (m==null) {
	    	        //failed
		            throw new ApplicationException("Unable to parse line: " + line);
	        	}
	        	
	        	ftpinfo item=new ftpinfo();
	    	    item.filename = m.Groups["name"].Value.Trim('\r');
	            item.path = path;
	            item.size = Convert.ToInt64(m.Groups["size"].Value);
	            item.permission = m.Groups["permission"].Value;
	            string _dir = m.Groups["dir"].Value;
	            if(_dir.Length>0  && _dir != "-")
	            {
	                item.fileType = directoryEntryTypes.directory;
	            } 
	            else
	            {
	                item.fileType = directoryEntryTypes.file;
	            }
	
	            try
	            {
	            	item.fileDateTime = DateTime.Parse(m.Groups["timestamp"].Value);
	            }
	            catch
	            {
	                item.fileDateTime = DateTime.MinValue; //null;
	            }
	            
	            files.Add(item);
			}
			
			return files;
		}
		
		public void download(fileinfo file)
		{
			System.IO.FileInfo info=new FileInfo(file.destination);
            if (info.Exists)
                info.Delete();
			FtpWebRequest request=(FtpWebRequest)FtpWebRequest.Create(file.completeFileName);
			request.Method=WebRequestMethods.Ftp.DownloadFile;
			request.UseBinary=true;
            request.Credentials = new NetworkCredential(_username, _password);
            FtpWebResponse response = (FtpWebResponse)request.GetResponse();
			Stream rs= response.GetResponseStream();
			FileStream fs= info.OpenWrite();
			try
			{
				int bufferSize=2048;
				byte[] bytes=new byte[bufferSize];
				int totBytes=0;
				int read=0;
				do
				{
					read = rs.Read(bytes,0,bufferSize);
					fs.Write(bytes,0,read);
					totBytes+=read;
					//TODO: Raise status event to notify the bytes transferred
                    OnDownloadProgress(file.completeFileName, totBytes);
                } while (read == bufferSize);
                OnDownloadComplete(file.completeFileName);
			}
			catch{}
			finally{
				fs.Close();
				rs.Close();
			}
		}
		
		public void upload(fileinfo file)
		{
			System.IO.FileInfo info=new FileInfo(file.completeFileName);
            string filename = file.completeFileName.Substring(file.completeFileName.LastIndexOf(@"\")+1);
            FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(file.destination + "/" + filename);
            request.Credentials = new NetworkCredential(_username, _password);
			request.Method=WebRequestMethods.Ftp.UploadFile;
			request.UseBinary=true;
			request.ContentLength=info.Length;
			
			int bufferSize=2048;
			byte[] bytes=new byte[bufferSize];
			int read=0;
			long totBytes=0;
			Stream rs=request.GetRequestStream();
			using(FileStream fs=info.OpenRead())
			{
				try
				{
					do
					{
						read = fs.Read(bytes,0,bufferSize);
						rs.Write(bytes,0,read);
						totBytes+=read;
						//TODO: Raise status event to notify the bytes transferred
                        OnUploadProgress(file.completeFileName,totBytes);
                    } while (read == bufferSize);
                    OnUploadComplete(file.completeFileName);
                }
				catch {}
				finally
				{
					fs.Close();
				}
			}
			rs.Close();
			request=null;
			return;
		}

		private Match GetMatchingRegex(string line)
	    {
	    	string[] formats  = { 
	                    @"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\w+\s+\w+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{4})\s+(?<name>.+)" ,
	                    @"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\d+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{4})\s+(?<name>.+)" ,
	                    @"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\d+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{1,2}:\d{2})\s+(?<name>.+)" ,
	                    @"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\w+\s+\w+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{1,2}:\d{2})\s+(?<name>.+)" ,
	                    @"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})(\s+)(?<size>(\d+))(\s+)(?<ctbit>(\w+\s\w+))(\s+)(?<size2>(\d+))\s+(?<timestamp>\w+\s+\d+\s+\d{2}:\d{2})\s+(?<name>.+)" ,
	                    @"(?<timestamp>\d{2}\-\d{2}\-\d{2}\s+\d{2}:\d{2}[Aa|Pp][mM])\s+(?<dir>\<\w+\>){0,1}(?<size>\d+){0,1}\s+(?<name>.+)"};
	        Regex rx;
	        Match m;
	        for(int i=0;i<formats.Length;i++)  //As Integer = 0 To formats.Length - 1
	        {
	        	rx = new Regex(formats[i]);
	            m = rx.Match(line);
	            if (m.Success)
	            {
	                return m;
	        	}
	        }
	        return null;
	    }
	}
}
